using System;
using UnityEngine;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Playables;
using AnimCollections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace DA.AnimGraph {
#if UNITY_EDITOR
    [System.Diagnostics.DebuggerTypeProxy(typeof(NodeLinkDebugView))]
#endif
    [BurstCompile]
    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct NodeLink : IDisposable {

        #region VirtualTable

        // Init 非必须，按override方式调用
        internal delegate void InitDelegate(in void* node, in InitContext i);

        // BuildPlayableGraph 必须

        // Evaluate 必须
        internal delegate void EvaluateDelegate(in void* node, bool onEnter, in EvaluateContext e, in GraphContext g, ref UpstreamData u, ref ScopeContext s);

        // OnExit 非必须，按override方式调用
        internal delegate void OnExitDelegate(in void* node, ref UpstreamData u);

        // PostEvaluate 非必须，按override方式调用
        internal delegate void PostEvaluateDelegate(in void* node, in EvaluateContext e, in GraphContext g, ref UpstreamData u, ref PostScopeContext p);

        // Apply 非必须，按override方式调用
        internal delegate void ApplyDelegate(in void* node);

        // Dispose 非必须，按event方式调用，返回true会统一处理子节点调用
        internal delegate bool DisposeDelegate(in void* node);

        // GetTimeState 非必须
        internal delegate IntPtr GetTimeStateDelegate(in void* node);

        #endregion

        public bool isValid => nodePtr != null;
        public bool isTimeBased => (nodeFlag & NodeFlag.Sample) != NodeFlag.None;
        public int childrenCount => children.Length;
        public NodeLink* selfPtr => (NodeLink*)UnsafeUtility.AddressOf(ref this);

        //public ByteBool isConnected;
        // size 232 168
        internal NodeVirtualTable virtualFunction; // 56
        public AUID nodeUuid; // 8
        private void* nodePtr; // 8
        public Playable playable; // 16
        private UnsafePtrList<PerkLink> perks; // 8*3
        private TimeState* timeState; // 8

        public AGRuntimeType nodeType; //2
        public NodeFlag nodeFlag; // 2 todo:isTimeBased改为设置值避免多次计算，若nodeFlag没有别的作用，就移除此字段
        private int lastEvaluateCounter; // 4
        [MarshalAs(UnmanagedType.U1)] private bool canSkipPostEvaluate; // 1

        internal NativeSimpleArray<IntPtr> children; // NodeLink*
        internal NativeSimpleArray<float> weights; // 16 ，子节点数量的2倍

        public void Dispose() {
            if (perks.IsCreated) {
                for (int i = 0; i < perks.Length; i++) {
                    perks[i]->Dispose();
                }
                perks.Dispose();
            }

            bool childrenNeedDispose = true; // 子节点需要释放（节点内部已经处理则不需要了，内置的子节点需要节点自己释放，然后置为false）
            if (virtualFunction.DisposeFunctionPtr != null) {
                childrenNeedDispose = virtualFunction.DisposeFunctionCall(nodePtr);
            }
            if (childrenNeedDispose) {
                for (int i = 0; i < childrenCount; i++) {
                    var child = (NodeLink*)children[i];
                    child->Dispose();
                }
            }
            if (playable.IsValid()) {
                playable.Destroy();
            }

            children.Dispose();
            weights.Dispose();

            nodePtr = null;
            timeState = null;
        }

        internal void SetupNode(void* nodePtr) {
#if UNITY_EDITOR
            Debug.Assert(nodePtr != null, "nodePtr cannot be null");
            Debug.Assert(virtualFunction.buildPlayableGraphFunctionPtr != null, "BuildPlayableGraphFunction cannot be null");
            Debug.Assert(virtualFunction.EvaluateFunctionPtr != null, "EvaluateFunction cannot be null");
#endif
            this.nodePtr = nodePtr;
        }

        internal void SetTimeState(in TimeState timeState) {
            fixed (TimeState* timeStatePtr = &timeState) {
                this.timeState = timeStatePtr;
            }
        }

        /// <summary>
        /// 此方法的目标是一次性将所有子节点设置完毕，确保输入数量和子节点数量一样
        /// 在放入 AnimGraph 之前必须设置完，之后不能再动
        /// </summary>
        public void SetChildren(params NodeLink*[] nodes) {
            if (childrenCount == 0 || childrenCount != nodes.Length) {
#if UNITY_EDITOR
                Debug.Assert(false, "param count mismatch");
#endif
                return;
            }
            fixed (NodeLink** ptr = nodes) {
                AnimNativeUtility.FillNativeArray(children, (void*)ptr, childrenCount * sizeof(IntPtr));
            }
        }

        /// <summary>
        /// 此方法的目标是一次性将所有子节点设置完毕，确保输入数量和子节点数量一样
        /// 在放入 AnimGraph 之前必须设置完，之后不能再动
        /// </summary>
        public void SetChildren(NodeLink** nodes, int count) {
            if (childrenCount == 0 || childrenCount != count) {
#if UNITY_EDITOR
                Debug.Assert(false, "param count mismatch");
#endif
                return;
            }
            AnimNativeUtility.FillNativeArray(children, (void*)nodes, childrenCount * sizeof(IntPtr));
        }

        /// <summary>
        /// 设置子节点，一个个设置没有检查。注意不能漏设置
        /// </summary>
        public void SetChild(NodeLink* node, int index) {
            children[index] = new(node);
        }

        internal void Init(in InitContext initContext) {
#if UNITY_EDITOR
            // 检查子节点不能为null，但是SlotNode子节点为null是正常的
            //for (int i = 0; i < childrenCount; i++) {
            //    Debug.Assert(children[i].ToPointer() != null, $"child[{i}] of NodeLink is null");
            //}
#endif
            lastEvaluateCounter = int.MinValue;
            if (virtualFunction.InitFunctionPtr != null) {
                virtualFunction.InitFunctionCall(nodePtr, initContext);
            } else {
                for (int i = 0; i < childrenCount; i++) {
                    var child = (NodeLink*)children[i];
                    child->Init(initContext);
                }
            }
        }

        internal Playable BuildPlayableGraph(in PlayableGraph graph, in BuildContext buildContext) {
            playable = virtualFunction.BuildPlayableGraphFunction(nodePtr, selfPtr, graph, buildContext);
            playable.SetSpeed(0);
            //isConnected = ByteBool.True;
            return playable;
        }

        [BurstCompile]
        internal static void Evaluate(NodeLink* self, in EvaluateContext evaluateContext, in GraphContext graphContext, ref UpstreamData upstream, ref ScopeContext scope) {
            self->CacheAndClearWeights();

            bool onEnter = scope.forceOnEnter || (self->lastEvaluateCounter + 1 != evaluateContext.evaluateCounter);
            if (self->isTimeBased) {
                if (onEnter) {
                    self->timeState->Reset();
                }
                self->timeState->Start();
            }

            self->virtualFunction.EvaluateFunctionCall(self->nodePtr, onEnter, evaluateContext, graphContext, ref upstream, ref scope);

            for (int i = 0; i < self->childrenCount; i++) {
                if (self->weights[i] == 0 && self->weights[i + self->childrenCount] > 0) {
                    var child = (NodeLink*)self->children[i];
                    child->OnExit(ref upstream);
                }
            }

            if (self->perks.IsCreated) {
                for (int i = 0; i < self->perks.Length; i++) {
                    self->perks[i]->Evaluate(self->GetTimeState(), onEnter, evaluateContext, graphContext, ref upstream, ref scope);
                }
            }

            self->canSkipPostEvaluate = scope.canSkipPostEvaluate;
            self->lastEvaluateCounter = evaluateContext.evaluateCounter;
        }

        internal void OnExit(ref UpstreamData upstream) {
            if (perks.IsCreated) {
                for (int i = 0; i < perks.Length; i++) {
                    perks[i]->OnExit(GetTimeState(), ref upstream);
                }
            }

            if (virtualFunction.OnExitFunctionPtr != null) {
                virtualFunction.OnExitFunctionCall(nodePtr, ref upstream);
            } else {
                for (int i = 0; i < childrenCount; i++) {
                    if (weights[i] > 0) {
                        var child = (NodeLink*)children[i];
                        child->OnExit(ref upstream);
                    }
                }
            }
            ClearWeights();
        }

        [BurstCompile]
        internal static void PostEvaluate(NodeLink* self, in EvaluateContext evaluateContext, in GraphContext graphContext, ref UpstreamData upstream, ref PostScopeContext postScope) {
            if (self->canSkipPostEvaluate) return;

            if (self->perks.IsCreated) {
                for (int i = 0; i < self->perks.Length; i++) {
                    self->perks[i]->PostEvaluate(self->GetTimeState(), evaluateContext, graphContext, ref upstream, ref postScope);
                }
            }

            if (self->virtualFunction.PostEvaluateFunctionPtr != null) {
                self->virtualFunction.PostEvaluateFunctionCall(self->nodePtr, in evaluateContext, in graphContext, ref upstream, ref postScope);
            } else {
                for (int i = 0; i < self->childrenCount; i++) {
                    if (self->weights[i] > 0) {
                        var childScope = new PostScopeContext(ref postScope, self->weights[i]);
                        var child = (NodeLink*)self->children[i];
                        PostEvaluate(child, evaluateContext, graphContext, ref upstream, ref childScope);
                        postScope.Fusion(ref childScope);
                    }
                }
            }
        }

        [BurstCompile]
        internal static void Apply(NodeLink* self) {
            if (self->virtualFunction.ApplyFunctionPtr != null) {
                self->virtualFunction.ApplyFunctionCall(self->nodePtr);
            } else {
                for (int i = 0; i < self->childrenCount; i++) {
                    float w = self->weights[i];
                    self->playable.SetInputWeight(i, w);
                    if (w != 0) {
                        var child = (NodeLink*)self->children[i];
                        if (child != null)
                            Apply(child);
                    }
                }
            }
        }

        internal TimeState* GetTimeState() {
            if (isTimeBased) {
                return timeState;
            } else {
                // 获取子节点的 TimeState（传递进度）
                if (virtualFunction.GetTimeStateFunctionPtr != null) {
                    // 由具体节点类型的实现决定传递哪个子节点
                    var timeStatePtr = virtualFunction.GetTimeStateFunctionCall(nodePtr);
                    return (TimeState*)timeStatePtr;
                } else {
                    if (childrenCount == 1) {
                        var child0 = (NodeLink*)children[0];
                        return child0->GetTimeState();
                    }
                    // 默认传递权重最大的子节点
                    int maxIndex = 0;
                    int maxWeight = 0;
                    for (int i = 0; i < childrenCount; i++) {
                        var w = weights[i];
                        if (w > maxWeight) {
                            maxIndex = i;
                        }
                        if (maxWeight > 0.5f) {
                            break;
                        }
                    }
                    var child = (NodeLink*)children[maxIndex];
                    return child->GetTimeState();
                }
            }
        }

        public void AddPerk(PerkLink* perk) {
            // if (!isTimeBased) return;

            if (!perks.IsCreated) {
                perks = new UnsafePtrList<PerkLink>(2, Allocator.Persistent);
            }
            perks.Add(perk);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CacheAndClearWeights() {
            if (childrenCount == 0) return;
            AnimNativeUtility.CacheAndClear(weights, childrenCount * sizeof(float));
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ClearWeights() {
            if (childrenCount == 0) return;
            AnimNativeUtility.Clear(weights, childrenCount * 2 * sizeof(float));
        }

        public void Dry(DryParam dryParam) {
            // 1 先递归处理子元素
            if (perks.IsCreated) {
                for (int i = 0; i < perks.Length; i++) {
                    perks[i]->Dry(dryParam);
                }
            }
            for (int i = 0; i < childrenCount; i++) {
                var child = ((NodeLink*)children[i]);
                if (child != null)
                    child->Dry(dryParam);
            }
            // 2 自己对应的node
            DryUtility.SwitchDryNode(nodeType, nodePtr, dryParam);
            // 3 自己内部数据
            DryUtility.DryPtr(ref nodePtr, dryParam);
            DryUtility.DryPerksPtrList(ref perks, dryParam);
            DryUtility.DryArray(ref weights, dryParam);
            if (nodeType == AGRuntimeType.SlotNode)
                DryUtility.DryArray(ref children, dryParam);
            else
                DryUtility.DryPtrArray(ref children, dryParam);
        }

        public void Wet(DryParam dryParam) {
            virtualFunction = AGRuntimeTypeRegister.GetInstance().GetNodeVirtualTable(nodeType);

            DryUtility.WetPtr(ref nodePtr, dryParam);
            DryUtility.WetPerksPtrList(ref perks, dryParam);
            DryUtility.WetArray(ref weights, dryParam);
            if (nodeType == AGRuntimeType.SlotNode)
                DryUtility.WetArray(ref children, dryParam);
            else
                DryUtility.WetPtrArray(ref children, dryParam);

            DryUtility.SwitchWetNode(nodeType, nodePtr, dryParam);

            for (int i = 0; i < childrenCount; i++) {
                var child = ((NodeLink*)children[i]);
                if (child != null)
                    child->Wet(dryParam);
            }
            if (perks.IsCreated) {
                for (int i = 0; i < perks.Length; i++) {
                    perks[i]->Wet(dryParam);
                }
            }
        }

#if UNITY_EDITOR
        internal class NodeLinkDebugView {
            private NodeLink target;

            public AUID nodeUuid => target.nodeUuid;
            public AGRuntimeType nodeType => target.nodeType;
            public TimeState timeState => *target.timeState;
            public bool isValid => target.isValid;
            public bool isTimeBased => target.isTimeBased;
            public bool canSkipPostEvaluate => target.canSkipPostEvaluate;
            public int lastEvaluateCounter => target.lastEvaluateCounter;
            public int childrenCount => target.childrenCount;
            public float[] weight => GetWeight();
            public float[] lastWeight => GetLastWeight();
            public NodeLink*[] children => GetChildren();

            public NodeLinkDebugView(NodeLink target) {
                this.target = target;
            }

            public float[] GetWeight() {
                var arr = new float[target.childrenCount];
                for (int i = 0; i < target.childrenCount; i++) {
                    arr[i] = target.weights[i];
                }
                return arr;
            }

            public float[] GetLastWeight() {
                var arr = new float[target.childrenCount];
                for (int i = 0; i < target.childrenCount; i++) {
                    arr[i] = target.weights[i + target.childrenCount];
                }
                return arr;
            }

            public NodeLink*[] GetChildren() {
                var arr = new NodeLink*[target.childrenCount];
                for (int i = 0; i < target.childrenCount; i++) {
                    arr[i] = (NodeLink*)target.children[i];
                }
                return arr;
            }
        }
#endif
    }
}